
/*
 * HD.cpp
 *
 *  Created on: Jul 7, 2009
 *      Author: elkobir
 */

#include "HD.h"


HD::HD(int dataBlockSize, int numberOfInodes, unsigned long int diskSize) {
	_fd = open("FILE_SYS", O_CREAT | O_TRUNC | O_RDWR, 0666);
	//cout << "fd = " << _fd << endl;

	_metadataBlock = new MetadataBlock(dataBlockSize, numberOfInodes, diskSize);
	_BlockSizeInBytes = _metadataBlock->_blockSizeInBytes;

	initAll();

	initializeRoot();
}

HD::HD() {
	_fd = open("FILE_SYS", O_RDWR, 0666);
	cout << "fd" << _fd << endl;

	// load _BlockSizeInBytes from file:
	unsigned char *bytes = Utils::InitBytes(8);
	pread(_fd, bytes, 8, 0);
	_BlockSizeInBytes = Utils::GetInt(bytes, 4);
	cout << "blockSize : " << _BlockSizeInBytes << endl;
	delete bytes;

	reload();

	ostringstream str;
	str << "fileSystemSizeInBlocks = " << _metadataBlock->_fileSystemSizeInBlocks <<  endl;
	str << "SuperBlocksPlusINodeArrayBlocksCount = " << _metadataBlock->getSuperBlocksPlusINodeArrayBlocksCount() <<  endl;
	Log::log(str, 0, "init");

	pthread_mutex_init(&_lock, NULL);

}

HD::~HD() {
	pthread_mutex_destroy(&_lock);
	close(_fd);
}

void HD::initFreeBlocks(){
	_freeBlocksBlock = new BlockNode(_BlockSizeInBytes);

	int totalFreeBlockCount = _metadataBlock->_fileSystemSizeInBlocks;

	int firstFreeBlockAddress = _metadataBlock->getSuperBlocksPlusINodeArrayBlocksCount();

	for (int i = firstFreeBlockAddress; i < totalFreeBlockCount; i++){
		addToFreeBlocksList(i);
	}
}

void HD::initFreeInodes(){
	_freeINodesBlock = new BlockNode(_BlockSizeInBytes);

	int totalINodeCount = _metadataBlock->_totalINodeCount;

	for (int i = 1; i <= totalINodeCount; i++){
		addToFreeINodesList(i);
	}
}

 void HD::initAll(){
	ostringstream str;
	str << "fileSystemSizeInBlocks = " << _metadataBlock->_fileSystemSizeInBlocks <<  endl;
	str << "SuperBlocksPlusINodeArrayBlocksCount = " << _metadataBlock->getSuperBlocksPlusINodeArrayBlocksCount() <<  endl;
	Log::log(str, 0, "init");

	pthread_mutex_init(&_lock, NULL);

	initFreeBlocks();
	initFreeInodes();

	flush();

 }

// allocates a new i-node and returns its number
int HD::allocateInode(){
	enter();

	int result = allocateInodeUnsync();

	leave();
	return result; // -1 if not successful
}

int HD::allocateInodeUnsync(){
	int result = -1;

	if (_metadataBlock->getFreeInodeCount() == 0){
		ostringstream str; str << "HD::allocateInodeUnsync: freeINodesCount = 0" << endl;
		Log::log(str, 0, "error");
		return -1;
	}

	if (_freeINodesBlock->isEmpty()){
		// we need to do the "smart-switch":
		int nextBlockAddress = _freeINodesBlock->_nextBlockAddress;
		if (nextBlockAddress != 0){
			// we do the "smart-switch":
			BlockNode* nextBlock = loadBlockNode(nextBlockAddress);

			saveBlockNode(nextBlock, FREE_INODES_BLOCK);
			delete _freeINodesBlock;
			_freeINodesBlock = nextBlock;
			freeDataBlockUnsync(nextBlockAddress);
		}
	}

	if (!_freeINodesBlock->isEmpty()){
		result = _freeINodesBlock->removeAddress();
		_metadataBlock->decFreeInodeCount();
		INode* inode = new INode(); // zeroing inode
		if (result <= 0 | result > _metadataBlock->_totalINodeCount){
			cout << "HD::allocateInode: i_nodeNum is invalid =  " << result << endl;
		}
		saveINode(inode, result);
		delete inode;
	} else {
		ostringstream str; str << "HD::allocateInodeUnsync: inode allocation failed. " << endl;
		str << "freeINodesCount = " << _metadataBlock->getFreeInodeCount() << " , " << "freeBlocksCount = " << _metadataBlock->getFreeBlocksCount() << endl;

		Log::log(str, 0, "debug");
	}

	return result; // -1 if not successful
}

void HD::addToFreeINodesList(int i_nodeNum)
{
	if (i_nodeNum <= 0 | i_nodeNum > _metadataBlock->_totalINodeCount){
		ostringstream str; str << "HD::addToFreeINodesList: i_nodeNum is invalid =  " << i_nodeNum << endl;
		Log::log(str, 0, "debug");
	}

    if(_freeINodesBlock->isFull()){
        int newBlock = allocateDataBlockUnsync();
        if (newBlock == -1){
    		ostringstream str; str << "HD::addToFreeINodesList - last free inode block was full. cannot allocate new block." << endl;
    		Log::log(str, 0, "error");
        }
		ostringstream str; str << "freeinode is full. doing inode full-switch with block num: " << newBlock <<  endl;
		Log::log(str, 0, "inodeswitch");
        unsigned char *bytes = _freeINodesBlock->toBytes();
        writeBlockUnsync(newBlock, bytes);
        delete bytes;
        delete _freeINodesBlock;
        _freeINodesBlock = new BlockNode(_BlockSizeInBytes);
        _freeINodesBlock->_nextBlockAddress = newBlock;
        saveBlockNode(_freeINodesBlock, FREE_INODES_BLOCK);
    }
	ostringstream str; str << "inode added to free list: " << i_nodeNum <<  endl;
	Log::log(str, 0, "inodeswitch");

    _freeINodesBlock->addAddress(i_nodeNum);
    _metadataBlock->incFreeInodeCount();
}

void HD::addToFreeBlocksList(int dblockNum)
{
	freeDataBlockUnsync(dblockNum);
}

// frees the specified i-node and also frees all the data blocks pointed to by it (directly or indirectly).
void HD::freeInode(int i_nodeNum){
	enter();

	if (i_nodeNum <= 0 | i_nodeNum > _metadataBlock->_totalINodeCount){
		ostringstream str;
		str << "HD::freeInode: i_nodeNum is invalid =  " << i_nodeNum << endl;
		Log::log(str, 0, "error");
		leave();
		return;
	}

	INode* inode = loadINode(i_nodeNum);
	int type = inode->getType();
	if (type == FILE | type == SOFTLINK){
		int hLinks = inode->getHardLinksNum();
		if (hLinks != 0){
			ostringstream str;
			str << "HD::freeInode: cannot free inode. it has hardlink num=  " << hLinks << endl;
			Log::log(str, 0, "error");
			leave();
			return;
		}
	}

	// free the specified i-node:
    addToFreeINodesList(i_nodeNum);

    // free all the data blocks pointed directly or indirectly:
	if (inode->_indirect != 0){
		unsigned char *bytes = Utils::InitBytes(_BlockSizeInBytes);
		readBlockUnsync(inode->_indirect, bytes);

		vector<int>* blockList = inode->getBlockList(_BlockSizeInBytes, bytes);
		for (unsigned int i = 0; i < blockList->size(); i++){
			int blk = blockList->at(i);

			freeDataBlockUnsync(blk);
		}
		delete blockList;

		freeDataBlockUnsync(inode->_indirect);
	} else {
		vector<int>* blockList = inode->getBlockList(_BlockSizeInBytes, NULL);
		for (unsigned int i = 0; i < blockList->size(); i++){
			int blk = blockList->at(i);
			freeDataBlockUnsync(blk);
		}
		delete blockList;
	}

	delete inode;

	leave();
}

// allocates a data block and returns its number
int HD::allocateDataBlock(){
	enter();



	int result = allocateDataBlockUnsync();

    if (result == -1){
		ostringstream str; str << "HD::allocateDataBlock - cannot allocate new block." << endl;
		Log::log(str, 0, "error");
    }

	leave();

	return result; // -1 if not successful
}

int HD::allocateDataBlockUnsync(){

	int result = -1;

	if (_metadataBlock->getFreeBlocksCount() == 0){
		ostringstream str; str << "HD::allocateDataBlockUnsync: freeIBlocksCount = 0" << endl;
		Log::log(str, 0, "error");
		return -1;
	}

	if (_freeBlocksBlock->isEmpty()){
		// we need to do the "smart-switch":
		int nextBlockAddress = _freeBlocksBlock->_nextBlockAddress;
		if (nextBlockAddress != 0){
			// we do the "smart-switch":
			BlockNode* nextBlock = loadBlockNode(nextBlockAddress);

			saveBlockNode(nextBlock, FREE_BLOCKS_BLOCK);
			delete _freeBlocksBlock;
			_freeBlocksBlock = nextBlock;
			result = nextBlockAddress;
			ostringstream str; str << "HD::allocateDataBlockUnsync - allocating block that was previously used for the free list = " << result << endl;
			Log::log(str, 0, "debug");
		}
	}

	if (result == -1 && !_freeBlocksBlock->isEmpty()){
		result = _freeBlocksBlock->removeAddress();
		_metadataBlock->decFreeBlocksCount();
		unsigned char *bytes = Utils::InitBytes(_BlockSizeInBytes);
		writeBlockUnsync(result, bytes);
		delete bytes;
	}

	ostringstream str; str << "HD::allocateDataBlockUnsync - allocating data block = " << result << endl;
	Log::log(str, 0, "allocatedatablock");

	return result; // -1 if not successful
}


// frees the specified block. Use (and update) the maps in the super block in these allocate/free methods.
void HD::freeDataBlock(int dblock){
	enter();

	freeDataBlockUnsync(dblock);

	leave();
}

void HD::freeDataBlockUnsync(int dblock){
	if (_freeBlocksBlock->isFull()){
		ostringstream str; str << "freeblock is full. doing smart-switch with block num: " << dblock <<  endl;
		Log::log(str, 0, "smartswitch");
		unsigned char *bytes = _freeBlocksBlock->toBytes();
		writeBlockUnsync(dblock, bytes);
		delete _freeBlocksBlock;
		_freeBlocksBlock = new BlockNode(_BlockSizeInBytes);
		_freeBlocksBlock->_nextBlockAddress = dblock;
        saveBlockNode(_freeBlocksBlock, FREE_BLOCKS_BLOCK);
	} else {
		ostringstream str; str << "freeblock added: " << dblock <<  endl;
		Log::log(str, 0, "smartswitch");
		_freeBlocksBlock->addAddress(dblock);
		if (_metadataBlock == NULL){
			ostringstream str; str << "_metadataBlock == NULL" <<  endl;
			Log::log(str, 0, "error");
		}
		_metadataBlock->incFreeBlocksCount();
	}
}

// return the type of the file (normal/directory/soft-link).
int HD::getInodeType(int i_nodeNum){
	enter();

	if (i_nodeNum <= 0 | i_nodeNum > _metadataBlock->_totalINodeCount){
		ostringstream str;
		str << "HD::getInodeType: i_nodeNum is invalid =  " << i_nodeNum << endl;
		Log::log(str, 0, "error");
	}
	int result;

	INode* inode = loadINode(i_nodeNum);
	result = inode->getType();
	delete inode;

	leave();

	return result;
}

// set the type of the file (normal/directory/soft-link).
void HD::setInodeType(int i_nodeNum, int filetype){
	enter();

	if (i_nodeNum <= 0 | i_nodeNum > _metadataBlock->_totalINodeCount){
		ostringstream str;
		str << "HD::setInodeType: i_nodeNum is invalid =  " << i_nodeNum << endl;
		Log::log(str, 0, "error");
	}

	INode* inode = loadINode(i_nodeNum);
	inode->setType(filetype);
	saveINode(inode, i_nodeNum);
	delete inode;

	leave();

}

/* return the number of the i-th data block containing the data of this file
 * (for 0<=i<=9 this is just stored in the 10 pointers in this i-node, for i>9,
 * you must retrieve it from the indirect data block..). */
int HD::getDataBlock(int i_nodeNum, int i){
	enter();

	if (i_nodeNum <= 0 | i_nodeNum > _metadataBlock->_totalINodeCount){
		ostringstream str;
		str << "HD::getDataBlock: i_nodeNum is invalid =  " << i_nodeNum << endl;
		Log::log(str, 0, "error");
	}
	int result = 0;

	INode* inode = loadINode(i_nodeNum);

	if (i < 10){
		result = inode->getDataBlockAt(i);
	} else {
		if (inode->_indirect != 0){
			unsigned char *bytes = Utils::InitBytes(_BlockSizeInBytes);
			readBlockUnsync(inode->_indirect, bytes);
			INodeIndirect* iNodeIndirect = new INodeIndirect(_BlockSizeInBytes, bytes);
			result = iNodeIndirect->getBlockAt(i);
			delete bytes;
			delete iNodeIndirect;
		}
	}

	delete inode;

	leave();

	return result;

}

// sets the i-th data block of this file to be dblockNum.
void HD::setDataBlock(int i_nodeNum, int i, int dblockNum){
	enter();

	if (i_nodeNum <= 0 | i_nodeNum > _metadataBlock->_totalINodeCount){
		ostringstream str;
		str << "HD::setDataBlock: i_nodeNum is invalid =  " << i_nodeNum << endl;
		Log::log(str, 0, "error");
	}

	INode* inode = loadINode(i_nodeNum);

	if (i < 10){
		inode->setDataBlockAt(i, dblockNum);
		saveINode(inode, i_nodeNum);
	} else {
		if (inode->_indirect == 0){ // indirect is not allocated:
			int newBlock = allocateDataBlockUnsync();
		    if (newBlock == -1){
				ostringstream str; str << "HD::setDataBlock - block not allocated. failed allocating new block." << endl;
				Log::log(str, 0, "error");
		    }
			inode->_indirect = newBlock;
			saveINode(inode, i_nodeNum);
		}
		INodeIndirect* iNodeIndirect = loadIndirect(inode->_indirect);
		iNodeIndirect->setBlockAt(i, dblockNum);
		saveIndirect(iNodeIndirect, inode->_indirect);
		delete iNodeIndirect;
	}

	delete inode;

	leave();

}

// read the entire content of the dblockNum's and copy it into buf.
void HD::readBlock(int dblockNum, unsigned char* buf){
	enter();

	readBlockUnsync(dblockNum, buf);

	leave();

}

void HD::readBlockUnsync(int dblockNum, unsigned char* buf){

	int offset = dblockNum * _BlockSizeInBytes;

	pread(_fd, buf, _BlockSizeInBytes, offset);

}


/* overwrite the content of block with number dblockNum with the newdata block.
 * Assume newdata's length is equal to dataBlockSize. */
void HD::writeBlock(int dblockNum, unsigned char* newdata){
	enter();

	writeBlockUnsync(dblockNum, newdata);

	leave();

}
void HD::writeBlockUnsync(int dblockNum, unsigned char* newdata){

	int offset = dblockNum * _BlockSizeInBytes;

	pwrite(_fd, newdata, _BlockSizeInBytes, offset);

}

/* return the file size saved in the given i-node. This is the exact size of the file in bytes,
 * not the number of blocks used by this file. */
int HD::getFileSize(int i_nodeNum){
	enter();

	if (i_nodeNum <= 0 | i_nodeNum > _metadataBlock->_totalINodeCount){
		ostringstream str;
		str << "HD::getFileSize: i_nodeNum is invalid =  " << i_nodeNum << endl;
		Log::log(str, 0, "error");
	}

	int result;

	INode* inode = loadINode(i_nodeNum);
	result = inode->getFileSize();
	delete inode;

	leave();

	return result;
}

// set the file size.
void HD::setFileSize(int i_nodeNum, int newSize){
	enter();

	if (i_nodeNum <= 0 | i_nodeNum > _metadataBlock->_totalINodeCount){
		ostringstream str;
		str << "HD::setFileSize: i_nodeNum is invalid =  " << i_nodeNum << endl;
		Log::log(str, 0, "error");
	}

	INode* inode = loadINode(i_nodeNum);
	inode->setFileSize(newSize);
	saveINode(inode, i_nodeNum);
	delete inode;

	leave();
}

BlockNode* HD::loadBlockNode(int dblockNum){
	unsigned char *bytes = Utils::InitBytes(_BlockSizeInBytes);
	readBlockUnsync(dblockNum, bytes);
	BlockNode* result = new BlockNode(_BlockSizeInBytes, bytes);
	delete bytes;
	return result;
}


void HD::saveBlockNode(BlockNode* blockNode, int dblockNum){
	unsigned char *bytes = blockNode->toBytes();
	writeBlockUnsync(dblockNum, bytes);
	delete bytes;
}

INodeIndirect* HD::loadIndirect(int dblockNum){
	unsigned char *bytes = Utils::InitBytes(_BlockSizeInBytes);
	readBlockUnsync(dblockNum, bytes);
	INodeIndirect* result = new INodeIndirect(_BlockSizeInBytes, bytes);
	delete bytes;
	return result;
}

void HD::saveIndirect(INodeIndirect* indirect, int dblockNum){
	unsigned char *bytes = indirect->toBytes();
	writeBlockUnsync(dblockNum, bytes);
	delete bytes;
}

INode* HD::loadINode(int i_nodeNum){
	if (i_nodeNum <= 0 | i_nodeNum > _metadataBlock->_totalINodeCount){
		ostringstream str;
		str << "HD::loadINode: i_nodeNum is invalid =  " << i_nodeNum << endl;
		Log::log(str, 0, "error");
	}
	unsigned char *bytes = Utils::InitBytes(_BlockSizeInBytes);

	int offset = 3 * _BlockSizeInBytes;
	offset += INODESIZE * i_nodeNum;
	pread(_fd, bytes, INODESIZE, offset);

	INode* res = new INode(bytes);

	delete bytes;
	return res;
}

void HD::saveINode(INode* i_node, int i_nodeNum){
	if (i_nodeNum <= 0 | i_nodeNum > _metadataBlock->_totalINodeCount){
		ostringstream str;
		str << "HD::saveINode: i_nodeNum is invalid =  " << i_nodeNum << endl;
		Log::log(str, 0, "error");
	}
	unsigned char *bytes = i_node->toBytes();
	int offset = 3 * _BlockSizeInBytes;
	offset += INODESIZE * i_nodeNum;
	pwrite(_fd, bytes, INODESIZE, offset);
	delete bytes;
}

void HD::reload(){

	unsigned char *bytes = Utils::InitBytes(_BlockSizeInBytes);
	readBlockUnsync(0, bytes);
	_metadataBlock = new MetadataBlock(bytes);

	readBlockUnsync(1, bytes);
	_freeINodesBlock = new BlockNode(_BlockSizeInBytes, bytes);

	readBlockUnsync(2, bytes);
	_freeBlocksBlock = new BlockNode(_BlockSizeInBytes, bytes);

	delete bytes;
}

void HD::flush(){

	unsigned char *bytes;

	if (_metadataBlock != NULL){
		bytes = _metadataBlock->toBytes();
		writeBlockUnsync(0, bytes);
		delete bytes;
		delete _metadataBlock;
	}
	if (_freeINodesBlock != NULL){
		bytes = _freeINodesBlock->toBytes();
		writeBlockUnsync(1, bytes);
		delete bytes;
		delete _freeINodesBlock;

	}
	if (_freeBlocksBlock != NULL){
		bytes = _freeBlocksBlock->toBytes();
		writeBlockUnsync(2, bytes);
		delete bytes;
		delete _freeBlocksBlock;
	}

}
void HD::enter(){
	//pthread_mutex_lock(&_lock);
	reload();
}

void HD::leave(){
	flush();
	//TODO pthread_mutex_unlock(&_lock);
}

int HD::getMaxFileSizeInBytes(){

	reload();

	int result = 0;
	int addressesInIndirectCount = (_BlockSizeInBytes / 4);
	int addressesInINodeCount = addressesInIndirectCount + 10;

	result = addressesInINodeCount * _BlockSizeInBytes;

	return result;
}

int HD::getTotalINodeCount(){

	reload();

	int result = _metadataBlock->_totalINodeCount;

	return result;
}

int HD::getRootInodeNum(){

	reload();

	int result = _metadataBlock->_rootINode;

	return result;

}

int HD::initializeRoot(){

	int rootInodeNum = allocateInode();
	//cout << "rootInodeNum = " << rootInodeNum << endl;
	setInodeType(rootInodeNum, DIRECTORY);
	setFileSize(rootInodeNum, 0);
	reload();
	_metadataBlock->_rootINode = rootInodeNum;
	flush();
}


int HD::incHardLinks(int i_nodeNum){
	enter();
	ostringstream str; str << "HD::incHardLinks with inode num: " << i_nodeNum <<  endl;
	Log::log(str, 0, "hardlinks");

	INode* inode = loadINode(i_nodeNum);

	int result = inode->incHardLinksNum();

	saveINode(inode, i_nodeNum);
	delete inode;

	leave();

	return result;
}


int HD::decHardLinks(int i_nodeNum){
	enter();
	ostringstream str; str << "HD::decHardLinks with inode num: " << i_nodeNum <<  endl;
	Log::log(str, 0, "hardlinks");

	INode* inode = loadINode(i_nodeNum);

	int result = inode->decHardLinksNum();

	saveINode(inode, i_nodeNum);
	delete inode;

	leave();

	return result;
}















